/*
 * qv_fileIO.c
 */

#include "qv_fileIO.h"
#include "qv_utils.h"
#include "qv_geneticEnvironment.h"
#include "qv_geneticAlgorithm.h"
#include "qv_testEnvironment.h"
#include "../IniParser/iniparser.h"
#include "../IniParser/dictionary.h"

static QV_settings *qvSettings = NULL;


/*
 * reads current round number from <QV_ROUNDNUM_FILE>
 * (returns initial value 0 if file does not exist)
 */
int readRoundNum(void) {
	FILE *roundNumFile;
	int roundNum = 0;

	if ((roundNumFile = fopen(QV_ROUNDNUMFILE, "r"))) {
		fscanf(roundNumFile, "%d", &roundNum);
		fclose(roundNumFile);
	}

	return roundNum;
}

/*
 * writes current round number to <QV_ROUNDNUM_FILE>
 */
void writeRoundNum(int roundNum) {
	FILE *roundNumFile;

	if ((roundNumFile = fopen(QV_ROUNDNUMFILE, "w"))) {
		fprintf(roundNumFile, "%d", roundNum);
		fclose(roundNumFile);
	}
}


/*
 * Reads in the unnormalized genes
 */
QV_genData *readGenData(int roundNum) {
	char *botName;
	char *botType;
	char *genFileName = malloc(sizeof(char) * LINE_BUF_SIZE);

	QV_genData *genData = malloc(sizeof(struct QV_genData) * getSettings()->bots);
	int i;

	/* In the first round only random bots, after that only children */
	if (roundNum == 1)
		botType = QV_RANDOM_BOT_STR;
	else
		botType = QV_CHILD_BOT_STR;

	/* Read in the .gen file for every bot */
	for (i = 0; i < getSettings()->bots; i++) {
		botName = generateBotName(roundNum, botType, i + 1);		
		
		sprintf(genFileName, "%s/%s.ini", QV_BOTGENESFILE_DIR, botName);
		readGenDataFile(genFileName, i, genData);

		free(botName);
	}
	
	free(genFileName);
	return genData;
}

/*
 * Reads in a specific gen data file
 */
void readGenDataFile(char *genFileName, int botNum, QV_genData *genData) {
	int i;
	char *genValue;
	dictionary *iniDict;
	
	char *iniPath = malloc(sizeof(char) * LINE_BUF_SIZE);	

	char genNamesBasic[QV_NUM_BOT_CHARS][LINE_BUF_SIZE] = QV_BOT_CHARACTS;
	char genNamesWeights[QV_NUM_ITEM_WEIGHTS][LINE_BUF_SIZE] = QV_ITEM_CHARACTS;
	
	if ((iniDict = iniparser_load(genFileName))) {			
		/* Read in all basic chars */
		for (i = 0; i < QV_NUM_BOT_CHARS; i++) {
			sprintf(iniPath, "%s:%s", "Basic", genNamesBasic[i]);
			genValue = iniparser_getstr(iniDict, iniPath);
			strcpy(genData[botNum].basic_gen_data[i], genValue);
		}

		/* Read in all weights */
		for (i = 0; i < QV_NUM_ITEM_WEIGHTS; i++) {
			sprintf(iniPath, "%s:%s", "Weights", genNamesWeights[i]);
			genValue = iniparser_getstr(iniDict, iniPath);
			strcpy(genData[botNum].item_gen_data[i], genValue);
		}

		iniparser_freedict(iniDict);
	}

	free(iniPath);
}

/*
 * Writes unnormalized genes to gene-files
 */
void writeGenData(int roundNum, QV_genData *genData) {
	char *genFileName = malloc(sizeof(char) * LINE_BUF_SIZE);
	FILE *genFile;
	dictionary *iniDict = NULL;

	char genNamesBasic[QV_NUM_BOT_CHARS][LINE_BUF_SIZE] = QV_BOT_CHARACTS;
	char genNamesWeights[QV_NUM_ITEM_WEIGHTS][LINE_BUF_SIZE] = QV_ITEM_CHARACTS;
	
	char *genVal = malloc(sizeof(char) * LINE_BUF_SIZE);
	char *iniPath = malloc(sizeof(char) * LINE_BUF_SIZE);

	int i, j;
	for (i = 0; i < getSettings()->bots; i++) {
		sprintf(genFileName, "%s/%s.ini", 
			QV_BOTGENESFILE_DIR, genData[i].basic_gen_data[CHARACTERISTIC_NAME]);

		if ((genFile = fopen(genFileName, "w"))) {			
            fprintf(genFile, "# Genes File: %s\n", 
                genData[i].basic_gen_data[CHARACTERISTIC_NAME]);
        
			if ((iniDict = dictionary_new(QV_NUM_BOT_CHARS + QV_NUM_ITEM_WEIGHTS))) {
				
				/* Add sections to the dictionary */
				dictionary_set(iniDict, "Basic", NULL);
				dictionary_set(iniDict, "Weights", NULL);
				dictionary_set(iniDict, "Parents", NULL);

				/* Add all basic chars to the dictionary */
				for (j = 0; j < QV_NUM_BOT_CHARS; j++) {					
					if (isStringCharacts(j)) {
						sprintf(iniPath, "%s:%s", "Basic", genNamesBasic[j]);
						sprintf(genVal, "\"%s\"", genData[i].basic_gen_data[j]);
						dictionary_set(iniDict, iniPath, genVal);
					}
					else {					
						sprintf(iniPath, "%s:%s", "Basic", genNamesBasic[j]);
						dictionary_set(iniDict, iniPath, genData[i].basic_gen_data[j]);
					}
				}

				/* Add all weights to the dictionary */
				for (j = 0; j < QV_NUM_ITEM_WEIGHTS; j++) {
					sprintf(iniPath, "%s:%s", "Weights", genNamesWeights[j]);
					dictionary_set(iniDict, iniPath, genData[i].item_gen_data[j]);
				}

				/* Write parents to ini file */
				dictionary_set(iniDict, "Parents:Parent1", genData[i].parent1);
				dictionary_set(iniDict, "Parents:Parent2", genData[i].parent2);

				/* Store the dictionary to an ini file */
				iniparser_dump_ini(iniDict, genFile);
				iniparser_freedict(iniDict);
			}

			fclose(genFile);
		}
	}

	free(genFileName);
	free(genVal);
	free(iniPath);
}


/*
 * Restore original bot entry file
 */
void restoreBotEntryFile(void) {
	FILE *botEntryFile, *botEntryFileOrg;
	char *lineBuff = malloc(sizeof(char) * LINE_BUF_SIZE);
	
	/* Restore original file */
	if ((botEntryFileOrg = fopen(QV_BOTALIASFILE_ORG, "r"))) {
		if ((botEntryFile = fopen(QV_BOTALIASFILE, "w"))) {
			while (fgets(lineBuff, LINE_BUF_SIZE, botEntryFileOrg)) {
				fputs(lineBuff, botEntryFile);
			}
			fclose(botEntryFile);
		}
		fclose(botEntryFileOrg);
	}

	free(lineBuff);
}

/*
 * Adds a new bot to the bots file.
 */
void addBotEntry(char* botName, char* botAIFileName) {
	FILE *botEntryFile;	

	/* Add new entry */
	if ((botEntryFile = fopen(QV_BOTALIASFILE, "a"))) {
		fprintf(botEntryFile, "\n{\nname %s\nmodel %s\naifile %s\n}\n",
			botName, QV_BOT_MODEL, botAIFileName);
		fclose(botEntryFile);
	}
}

/*
 * writes normalized characteristics of bot to Quake AI-file
 */
void writeBotAIFile(char **botNormBasicGens) {
	char *botAIFileNamePart = malloc(sizeof(char) * LINE_BUF_SIZE);
	char *botAIFileName = malloc(sizeof(char) * LINE_BUF_SIZE);
	
	char genNames[QV_NUM_BOT_CHARS][LINE_BUF_SIZE] = QV_BOT_CHARACTS;
	
	FILE *botAIFile;
	int i;

	sprintf(botAIFileNamePart, "bots/%s_c.c", botNormBasicGens[CHARACTERISTIC_NAME]);
	sprintf(botAIFileName, "%s/%s_c.c", QV_BOTAIFILE_DIR, botNormBasicGens[CHARACTERISTIC_NAME]);

	if ((botAIFile = fopen(botAIFileName, "w"))) {
		/* write AI-file header */
		fprintf(botAIFile, 
			"#include \"chars.h\"\n\n"
			"skill %s\n"
			"{\n", QV_BOT_SKILL_LVL);

		/* write all characteristics */
		for (i = 0; i < QV_NUM_BOT_CHARS; i++) {
			if (isStringCharacts(i)) {
				fprintf(botAIFile, "\t%-50s\"%s\"\n",
					genNames[i],
					botNormBasicGens[i]);
			}
			else {			
				fprintf(botAIFile, "\t%-50s%s\n",
					genNames[i],
					botNormBasicGens[i]);
			}
		}

		/* end file */
		fprintf(botAIFile, "}\n");
		fclose(botAIFile);

		/* add bot to bots file */
		addBotEntry(botNormBasicGens[CHARACTERISTIC_NAME], botAIFileNamePart);
	}

	free(botAIFileNamePart);
	free(botAIFileName);
}

/*
 * writes item/weapon weights of bot to Quake AI-file
 */
void writeItemWeightsFile(char* botName, char **botNormWeaponWeightGens) {
	char *weightIFileName = malloc(sizeof(char) * LINE_BUF_SIZE);
	char *weightWFileName = malloc(sizeof(char) * LINE_BUF_SIZE);
	
	char genNames[QV_NUM_ITEM_WEIGHTS][LINE_BUF_SIZE] = QV_ITEM_CHARACTS;
	
	FILE *weightIFile;
	FILE *weightWFile;
	int i;

	sprintf(weightIFileName, "%s/%s_i.c", QV_BOTAIFILE_DIR, botName);
	sprintf(weightWFileName, "%s/%s_w.c", QV_BOTAIFILE_DIR, botName);

	if ((weightIFile = fopen(weightIFileName, "w")) && 
		(weightWFile = fopen(weightWFileName, "w"))) {
		/* write file header */
		fprintf(weightIFile, "#include \"inv.h\"\n\n");
		fprintf(weightWFile, "#include \"inv.h\"\n\n");

		for (i = 0; i < QV_NUM_ITEM_WEIGHTS; i++) {
			fprintf(weightIFile, "#define %-24s%s\n",
				genNames[i],
				botNormWeaponWeightGens[i]);

			if(i >= 2 && i <= 11) {
				fprintf(weightWFile, "#define %-24s%s\n",
					genNames[i],
					botNormWeaponWeightGens[i]);
			}
		}

		fprintf(weightIFile, "\n#include \"fw_items.c\"");
		fclose(weightIFile);

		fprintf(weightWFile, "\n#include \"fw_weap.c\"");
		fclose(weightWFile);
	}

	if (weightIFileName)
		free(weightIFileName);
	if (weightWFileName)
		free(weightWFileName);
}


/* 
 * Returns the settings struct, if it hasn't been instantiated it will
 * read the settings from the settings.ini file
 */
QV_settings *getSettings(void) {
	int i;
	char settingIds[QV_NUM_SETTINGS][LINE_BUF_SIZE] = QV_SETTING_IDS;

	dictionary *dict = iniparser_load(QV_SETTINGSFILE);
	
	if (qvSettings)
		return qvSettings;

	qvSettings = malloc(sizeof(struct QV_settings));

	for (i = 0; i < QV_NUM_SETTINGS; i++) {
		switch (i) {
			case QV_SETTING_BOTS:
				qvSettings->bots = iniparser_getint(dict, settingIds[i], QV_BOTS_PER_ROUND);
				break;
			case QV_SETTING_MAXROUNDS:
				qvSettings->maxrounds = iniparser_getint(dict, settingIds[i], QV_MAXROUNDS);
				break;
			case QV_SETTING_SWAPPOINT_RATE:
				qvSettings->swappoint_rate = (float)iniparser_getdouble(dict, settingIds[i], QV_SWAPPOINT_RATE);
				break;
			case QV_SETTING_CROSSOVER_RATE:
				qvSettings->crossover_rate = (float)iniparser_getdouble(dict, settingIds[i], QV_CROSSOVER_RATE);
				break;
			case QV_SETTING_MUTATION_MU:
				qvSettings->mutation_mu = (float)iniparser_getdouble(dict, settingIds[i], QV_MUTATION_MU);
				break;
			case QV_SETTING_MUTATION_SIGMA:
				qvSettings->mutation_sigma = (float)iniparser_getdouble(dict, settingIds[i], QV_MUTATION_SIGMA);
				break;
			case QV_SETTING_USE_WEIGHTS:
				qvSettings->use_weights = iniparser_getboolean(dict, settingIds[i], 1);
				break;
			case QV_SETTING_DEPENDENCIES:
				qvSettings->dependencies = iniparser_getboolean(dict, settingIds[i], QV_DEPENDENCIES);
				break;
			case QV_SETTING_SELECTION_MODEL:
				qvSettings->selection_model = iniparser_getint(dict, settingIds[i], QV_ROULETTE_SELECTION);
				break;
			case QV_SETTING_FITNESS_FUNCTION:
				qvSettings->fitness_function = iniparser_getint(dict, settingIds[i], 0);
				break;
			case QV_SETTING_TESTMODE:
				qvSettings->testmode = iniparser_getboolean(dict, settingIds[i], 0);
				break;
			case QV_SETTING_TESTROUNDS:
				qvSettings->testrounds = iniparser_getint(dict, settingIds[i], QV_TESTROUNDS);
				break;
			case QV_SETTING_TESTBOTS:
				qvSettings->testbots = iniparser_getint(dict, settingIds[i], QV_BOTS_PER_ROUND);
				break;
			default:
				break;
		}
	}

	if (dict)
		iniparser_freedict(dict);

	return qvSettings;
}

/*
 * Frees settings used in a round
 */
void freeSettings(void) {
	if (qvSettings) {
		free(qvSettings);
		qvSettings = NULL;
	}
}


/*
 * Returns all the names of the bots to run in the test round
 */
char **getTestBotNames(void) {
	FILE *testBotFile;
	char **botNames = malloc(sizeof(char*) * getSettings()->testbots);
	int i;

	if ((testBotFile = fopen(QV_TESTBOTSFILE, "r"))) {
		for (i = 0; i < getSettings()->testbots; i++) {
			botNames[i] = malloc(sizeof(char) * LINE_BUF_SIZE);
			fscanf(testBotFile, "%s\n", botNames[i]);
		}
		fclose(testBotFile);
	}

	return botNames;
}


/*
 * function for updating botlog CSV file
 * with statistics collected from each bot in round
 */
void updateBotLogFile(QV_botData *botData) {
	char *genFileName = malloc(sizeof(char) * LINE_BUF_SIZE);
	FILE *genFile;
	dictionary *iniDict = NULL;

	char *genVal = malloc(sizeof(char) * LINE_BUF_SIZE);

	int i;
	for (i = 0; i < getSettings()->bots; i++) {
		sprintf(genFileName, "%s/%s.ini", QV_BOTGENESFILE_DIR, botData[i].botAltName);
		
		if ((genFile = fopen(genFileName, "a"))) {
			iniDict = dictionary_new(3);
			
			/* Add section to the dictionary */
			dictionary_set(iniDict, "Stats", NULL);
			
			sprintf(genVal, "%i", botData[i].numFrags);
			dictionary_set(iniDict, "Stats:Frags",  genVal);

			sprintf(genVal, "%i", botData[i].numDeaths);
			dictionary_set(iniDict, "Stats:Deaths",  genVal);

			sprintf(genVal, "%i", botData[i].rank);
			dictionary_set(iniDict, "Stats:Ranking",  genVal);

			sprintf(genVal, "%i", botData[i].roundNum);
			dictionary_set(iniDict, "Stats:Round",  genVal);

			/* Store the dictionary to an ini file */
			iniparser_dump_ini(iniDict, genFile);
			iniparser_freedict(iniDict);

			fclose(genFile);
		}
	} 

	free(genFileName);
	free(genVal);
}
